﻿using System;
using System.Collections.Generic;
using System.Linq;
using YAXLib;
using RaytracerLib.Attenuations;
using RaytracerLib.Volumes;
using RaytracerLib.MathLib;

namespace RaytracerLib.Materials
{
    public class Material : SceneElement
    {
        [YAXNode(Default = null, OmitWhenDefault = true)]
        public IMaterialPart Ambient;

        [YAXNode(Default = null, OmitWhenDefault = true)]
        public IMaterialPart Diffuse;

        [YAXNode(Default = null, OmitWhenDefault = true)]
        public IMaterialPart SpecularHighlight;

        [YAXNode(Default = null, OmitWhenDefault = true)]
        public IMaterialPart SpecularHighlightExponent;

        [YAXNode(Default = null, OmitWhenDefault = true)]
        public IMaterialPart ReflectionTransmittance;

        [YAXNode(Default = null, OmitWhenDefault = true)]
        public IMaterialPart Tangents;

        [YAXNode(Default = null, OmitWhenDefault = true)]
        public IMaterialPart RefractionTransmittance;

        [YAXNode(Default = Double.NegativeInfinity, OmitWhenDefault = true)]
        public double RefractionIndice = Double.NegativeInfinity;

        [YAXNode(Default = null, OmitWhenDefault = true)]
        public Attenuation Attenuation;

        public Material()
        {
            Name = "Material";
        }

        public MaterialsList Materials
        {
            get
            {
                if (Scene == null)
                    return null;
                return Scene.Materials;
            }
        }

        protected override void RenderEnd(bool a_all)
        {
            base.RenderEnd(a_all);

            foreach (var mp in MaterialParts)
            {
                if (mp != null)
                    mp.RenderEnd(a_all);
            }
        }

        protected override void RenderStart(RenderStartPhase a_phase)
        {
            foreach (var mp in MaterialParts)
            {
                if (mp != null)
                    mp.RenderStart(a_phase);
            }

            if (a_phase == RenderStartPhase.CalculateAttenuations)
            {
                if (HasAttenuation)
                    Attenuation.CalculateAttenuation(Materials.Scene.RenderOptions.LightSpeed);
            }

            if (a_phase == RenderStartPhase.OptimizeMaterials)
                MaterialOptimizer.Optimize(this);
        }

        public override string ToString()
        {
            return String.Format("Name: {0}", Name);
        }

        public override bool Equals(object a_obj)
        {
            if (a_obj == null)
                return false;
            Material m = a_obj as Material;
            if ((object)m == null)
                return false;

            return YAXSerializer.Compare(this, m);
        }

        public override int GetHashCode()
        {
            return YAXSerializer.CalculateHashCode(this);
        }

        public bool HasTangents
        {
            get
            {
                return Tangents != null;
            }
        }

        public bool HasAmbient
        {
            get
            {
                return Ambient != null;
            }
        }

        public bool HasReflection
        {
            get
            {
                return ReflectionTransmittance != null;
            }
        }

        public bool HasAttenuation
        {
            get
            {
                return Attenuation != null;
            }
        }

        public bool HasRefraction
        {
            get
            {
                return !Double.IsNegativeInfinity(RefractionIndice) && (RefractionTransmittance != null);
            }
        }

        public bool HasDiffuse
        {
            get
            {
                return Diffuse != null;
            }
        }

        public bool HasSpecularHighlight
        {
            get
            {
                return (SpecularHighlight != null) && (SpecularHighlightExponent != null);
            }
        }

        public Material DeepClone()
        {
            return YAXSerializer.DeepClone(this);
        }

        internal IEnumerable<IMaterialPart> MaterialParts
        {
            get
            {
                yield return Ambient;
                yield return Diffuse;
                yield return SpecularHighlight;
                yield return SpecularHighlightExponent;
                yield return ReflectionTransmittance;
                yield return Tangents;
                yield return RefractionTransmittance;
            }
        }

        public void ReplaceMaterialPart(IMaterialPart a_old, IMaterialPart a_new)
        {
            if (Object.ReferenceEquals(a_old, Ambient))
                Ambient = a_new;
            else if (Object.ReferenceEquals(a_old, Diffuse))
                Diffuse = a_new;
            else if (Object.ReferenceEquals(a_old, SpecularHighlight))
                SpecularHighlight = a_new;
            else if (Object.ReferenceEquals(a_old, SpecularHighlightExponent))
                SpecularHighlightExponent = a_new;
            else if (Object.ReferenceEquals(a_old, ReflectionTransmittance))
                ReflectionTransmittance = a_new;
            else if (Object.ReferenceEquals(a_old, Tangents))
                Tangents = a_new;
            else if (Object.ReferenceEquals(a_old, RefractionTransmittance))
                RefractionTransmittance = a_new;
            else
                throw new InvalidOperationException();
        }

        public override bool Remove()
        {
            return Scene.Materials.Remove(this);
        }
    }
}